package dao

import (
	"context"
	rentalpb "coolcar/rental/api/gen/v1"
	"coolcar/shared/id"
	mgo "coolcar/shared/mongo"
	"coolcar/shared/mongo/objid"
	"fmt"

	"go.mongodb.org/mongo-driver/bson"
	"go.mongodb.org/mongo-driver/mongo"
)

const (
	tripField      = "trip"
	accountIDField = tripField + ".accountid"
	statusField    = tripField + ".status"
)

// Mongo defines a mongo dao.
type Mongo struct {
	col *mongo.Collection
}

// NewMongo creates a new mongo dao
func NewMongo(db *mongo.Database) *Mongo {
	return &Mongo{
		col: db.Collection("trip"),
	}
}

// TripRecord defines a trip record in mongo db.
type TripRecord struct {
	mgo.IDField        `bson:"inline"`
	mgo.UpdatedAtField `bson:"inline"`
	Trip               *rentalpb.Trip `bson:"trip"`
}

// CreateTrip creates a trip.
func (m *Mongo) CreateTrip(ctx context.Context, trip *rentalpb.Trip) (*TripRecord, error) {
	r := &TripRecord{
		Trip: trip,
	}
	r.ID = mgo.NewObjID()
	r.UpdatedAt = mgo.UpdatedAt()

	_, err := m.col.InsertOne(ctx, r)
	if err != nil {
		return nil, err
	}

	return r, nil
}

// GetTrip gets a trip.
func (m *Mongo) GetTrip(ctx context.Context, id id.TripID, accountID id.AccountID) (*TripRecord, error) {
	objID, err := objid.FromID(id)
	if err != nil {
		return nil, fmt.Errorf("invalid id:%v", err)
	}

	res := m.col.FindOne(ctx, bson.M{
		mgo.IDFieldName: objID,
		accountIDField:  accountID,
	})
	if err := res.Err(); err != nil {
		return nil, err
	}

	var tr TripRecord
	err = res.Decode(&tr)
	if err != nil {
		return nil, fmt.Errorf("connot decode: %v", err)
	}

	return &tr, nil
}

// GetTrips gets trips for the account by status.
// If status is not specified, gets all trips for the account.
func (m *Mongo) GetTrips(ctx context.Context, accountID id.AccountID, status rentalpb.TripStatus) ([]*TripRecord, error) {
	filter := bson.M{
		accountIDField: accountID,
	}
	if status != rentalpb.TripStatus_TS_NOT_SPECIFIED {
		filter[statusField] = status
	}

	res, err := m.col.Find(ctx, filter)
	if err != nil {
		return nil, err
	}

	var trips []*TripRecord
	for res.Next(ctx) {
		var trip TripRecord
		err = res.Decode(&trip)
		// 这里就有一个问题了。我们其中一行Decode出错，整个request算不算错呢？
		if err != nil {
			// 现在简单点，暂时先算它错。更加地道点呢，比如说这个选择总共有10行，其中有1行Decode出错了，我们应该把9行返回出去，再加上另外1行的错误。
			return nil, err
		}
		trips = append(trips, &trip)
	}
	return trips, nil
}

// UpdateTrip updates a trip.
func (m *Mongo) UpdateTrip(ctx context.Context, tid id.TripID, aid id.AccountID, updatedAt int64, trip *rentalpb.Trip) error {
	objID, err := objid.FromID(tid)
	if err != nil {
		return err
	}

	newUpdatedAt := mgo.UpdatedAt()
	// res, err := m.col.UpdateOne(ctx, bson.M{
	// 	mgo.IDFieldName:        objID,
	// 	accountIDField:         aid.String(),
	// 	mgo.UpdatedAtFieldName: updatedAt,
	// }, mgo.Set(bson.M{
	// 	tripField:              trip,
	// 	mgo.UpdatedAtFieldName: newUpdatedAt,
	// }))
	// if err != nil {
	// 	return err
	// }
	// if res.MatchedCount == 0 {
	// 	return mongo.ErrNoDocuments
	// }
	// return nil

	// 可以使用该方法
	res := m.col.FindOneAndReplace(ctx, bson.M{
		mgo.IDFieldName:        objID,
		accountIDField:         aid.String(),
		mgo.UpdatedAtFieldName: updatedAt,
	}, bson.M{
		tripField:              trip,
		mgo.UpdatedAtFieldName: newUpdatedAt,
	})
	if err := res.Err(); err != nil {
		return err
	}
	return nil
}
